package com.pindao.data.util;

import com.alibaba.fastjson.JSON;
import com.pindao.data.common.Result;
import com.pindao.data.entity.ReqEsDocument;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.*;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;


/**
 * @author LXB
 * @date 2021/4/22
 * @desc
 */
@Component
@Slf4j
public class ElasticsearchUtil {

    private static final RequestOptions COMMON_OPTIONS;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

        // 默认缓存限制为100MB，此处修改为30MB。
        builder.setHttpAsyncResponseConsumerFactory(
                new HttpAsyncResponseConsumerFactory
                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

    @Value("${spring.elasticsearch.cluster_host}")
    private String cluster_host;

    @Value("${spring.elasticsearch.cluster_port}")
    private Integer cluster_port;

    @Value("${spring.elasticsearch.username}")
    private String username;

    @Value("${spring.elasticsearch.password}")
    private String password;

    public RestHighLevelClient getEsClient() {
        // 阿里云Elasticsearch集群需要basic auth验证。
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        //访问用户名和密码,创建阿里云Elasticsearch实例时设置的用户名和密码，也是Kibana控制台的登录用户名和密码。
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(this.username, this.password));
        // 通过builder创建rest client，配置http client的HttpClientConfigCallback。
        // 单击所创建的Elasticsearch实例ID，在基本信息页面获取公网地址，即为ES集群地址。
        RestClientBuilder builder = RestClient.builder(new HttpHost(this.cluster_host, this.cluster_port))
                .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                    @Override
                    public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
                        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                    }
                });
        // RestHighLevelClient实例通过REST low-level client builder进行构造。
        RestHighLevelClient highClient = new RestHighLevelClient(builder);
        return highClient;
    }

    /**
     * 创建索引
     *
     * @param index
     * @throws IOException
     */
    public void createIndex(String index) throws IOException {
        if (!existsIndex(index)) {
            CreateIndexRequest request = new CreateIndexRequest(index);
            CreateIndexResponse createIndexResponse = getEsClient().indices().create(request, COMMON_OPTIONS);
            System.out.println("createIndex: " + JSON.toJSONString(createIndexResponse));
        }
    }

    /**
     * 判断索引是否存在
     *
     * @param index
     * @return
     * @throws IOException
     */
    public boolean existsIndex(String index) throws IOException {
        GetIndexRequest request = new GetIndexRequest(index);
        boolean exists = getEsClient().indices().exists(request, COMMON_OPTIONS);
        System.out.println("existsIndex: " + exists);
        return exists;
    }

    /**
     * 判断记录是都存在
     *
     * @param index
     * @param type
     * @return
     * @throws IOException
     */
    public boolean exists(String index, String type, ReqEsDocument document) throws IOException {
        GetRequest getRequest = new GetRequest(index, type, document.getId());
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        getRequest.storedFields("_none_");
        boolean exists = getEsClient().exists(getRequest, COMMON_OPTIONS);
        System.out.println("exists: " + exists);
        return exists;
    }

    /**
     * 添加数据
     *
     * @param index 索引
     * @param id    id
     */
    public String addData(ReqEsDocument document, String index, String id) {
        String Id = null;
        try {
            //index_name为索引名称；type_name为类型名称,7.0及以上版本必须为_doc；doc_id为文档的id。
            // 同步执行，并使用自定义RequestOptions（COMMON_OPTIONS）。
            RestHighLevelClient highClient = getEsClient();
            IndexRequest request = new IndexRequest(index, "_doc").id(id).source(JSON.toJSONString(document), XContentType.JSON);
            request.setPipeline("timestamp");
            IndexResponse response = highClient.index(request, COMMON_OPTIONS);
            Id = response.getId();
            highClient.close();
            log.info("索引:{},数据添加,返回码:{},id:{}", index, response.status().getStatus(), Id);
        } catch (IOException e) {
            log.error("添加数据失败,index:{},id:{}", index, id);
        }
        return Id;
    }

    /**
     * 修改数据
     *
     * @param index 索引
     * @param id    id
     */
    public String updateData(ReqEsDocument document, String index, String id) {
        String Id = null;
        try {
            RestHighLevelClient highClient = getEsClient();
            UpdateRequest request = new UpdateRequest(index, "_doc", id).doc(JSON.toJSONString(document), XContentType.JSON);
            UpdateResponse response = highClient.update(request, COMMON_OPTIONS);
            Id = response.getId();
            highClient.close();
            log.info("数据更新,返回码:{},id:{}", response.status().getStatus(), Id);
        } catch (IOException e) {
            log.error("数据更新失败,index:{},id:{}", index, id);
        }
        return Id;
    }

    /**
     * 批量插入数据
     *
     * @param index 索引
     * @param list  批量增加的数据
     */
    public Result insertBatch(String index, List<ReqEsDocument> list) {
        String state = null;
        BulkRequest request = new BulkRequest();
        list.forEach(item -> request.add(new IndexRequest(index, "_doc")
                .id(item.getId()).source(JSON.toJSONString(item), XContentType.JSON)));

        try {
            RestHighLevelClient highClient = getEsClient();
            request.pipeline("timestamp");
            BulkResponse bulk = highClient.bulk(request, COMMON_OPTIONS);
            int status = bulk.status().getStatus();
            state = Integer.toString(status);
            highClient.close();
            log.info("索引:{},批量插入{}条数据成功!", index, list.size());
        } catch (IOException e) {
            log.error(e.getMessage());

            log.error("索引:{},批量插入数据失败", index);
            return Result.fail("插入失败");
        }
        return Result.success("插入成功");
    }

    /**
     * 根据id删除数据
     *
     * @param index 索引
     * @param id    id
     */
    public Result deleteById(String index, String id) {
        String state = null;
        DeleteRequest request = new DeleteRequest(index, "_doc", id);
        try {
            DeleteResponse response = getEsClient().delete(request, COMMON_OPTIONS);
            int status = response.status().getStatus();
            state = Integer.toString(status);
            log.info("索引:{},根据id{}删除数据:{}", index, id, JSON.toJSONString(response));
        } catch (Exception e) {
            log.error("根据id删除数据失败,index:{},id:{}", index, id);
            return Result.fail("删除失败");
        }
        return Result.success("删除成功");
    }

    /**
     * 根据条件删除数据
     *
     * @param index   索引
     * @param builder 删除条件
     */
    public void deleteByQuery(String index, QueryBuilder builder) {
        DeleteByQueryRequest request = new DeleteByQueryRequest(index);
        request.setQuery(builder);
        //设置此次删除的最大条数
        request.setBatchSize(1000);
        try {
            getEsClient().deleteByQuery(request, COMMON_OPTIONS);
        } catch (Exception e) {
            log.error("根据条件删除数据失败,index:{}", index);
        }
    }


    /*
     * 根据条件查询数据
     *
     * @param index         索引
     * @param startPage     开始页
     * @param pageSize      每页条数
     * @param sourceBuilder 查询返回条件
     * @param queryBuilder  查询条件
     public Page<Map<String, Object>> searchDataPage(String index, int startPage, int pageSize,String companyId,String content,String... fieldNames) {
        SearchRequest request = new SearchRequest(index);
        request.types("_doc");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        QueryBuilder queryBuilder=createQueryBuilder(content, companyId, fieldNames);
        try {
            //加载查询条件
            sourceBuilder.query(queryBuilder);
            RestHighLevelClient highClient=getEsClient();

            long totalHits=searchDataPageCount(index, sourceBuilder, highClient);
            HighlightBuilder highlightBuilder=createHighlightBuilder(fieldNames);
            if (!ObjectUtils.isEmpty(highlightBuilder)){
                sourceBuilder.highlighter(highlightBuilder);
            }
            //设置超时时间
            sourceBuilder.timeout(new TimeValue(30, TimeUnit.SECONDS));
            //设置是否按匹配度排序
            sourceBuilder.explain(true);
            sourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC));


            request.source(sourceBuilder);

            //设置分页
            sourceBuilder.from((startPage - 1) * pageSize).size(pageSize);

            log.info("查询返回条件：" + sourceBuilder.toString());
            SearchResponse searchResponse = highClient.search(request, COMMON_OPTIONS);
            //Long totalHits = searchResponse.getHits().toXContent(builder, params);
            log.info("共查出{}条记录", totalHits);
            RestStatus status = searchResponse.status();
            if (status.getStatus() == 200) {
                List<Map<String, Object>> sourceList = new ArrayList<>();
                for (SearchHit searchHit : searchResponse.getHits().getHits()) {
                    Map<String,Object> map=new HashMap<>();
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    map.put("source",sourceAsMap);
                    // 处理高亮数据
                    Map<String,Object> hitMap = new HashMap<>();
                    searchHit.getHighlightFields().forEach((k,v) -> {
                        String hight = "";
                        for(Text text : v.getFragments()) hight += text.string();
                        hitMap.put(v.getName(),hight);
                    });
                    map.put("highlight",hitMap);
                    sourceList.add(map);
                }
                Page<Map<String, Object>> page = new Page<Map<String, Object>>();
                page.setPageNo(startPage);
                page.setPageSize(pageSize);
                page.setTotal((int)totalHits);
                page.setRows(sourceList);
                return page;
            }
            highClient.close();
        } catch (IOException e) {
            System.err.println(e.getMessage());
            log.error("条件查询索引{}时出错", index);
        }
        return null;
    }*/


    public long searchDataPageCount(String index, SearchSourceBuilder sourceBuilder, RestHighLevelClient highClient) {
        CountRequest countRequest = new CountRequest(index);
        countRequest.source(sourceBuilder);
        try {
            CountResponse response = highClient.count(countRequest, COMMON_OPTIONS);
            long length = response.getCount();
            return length;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /*public ApiResult originSearch(String name,int pageNo,int pageSize,String indexName) {
        ApiResult result=new ApiResult();

        String[] fieldNames=new String[] {"title"};
        Page<Map<String, Object>> page=searchDataPage(indexName,pageNo, pageSize,"0", name,fieldNames);
        return result.success(page);
    }*/

    /**
     * 构建高亮字段 进行 多字段模糊查询
     */
    private QueryBuilder createQueryBuilder(String content, String companyId, String... fieldNames) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //自定义条件(Operator.AND 表示匹配包含值)  
        boolQueryBuilder.must(QueryBuilders.multiMatchQuery(content, fieldNames).operator(Operator.OR).minimumShouldMatch("50%"));
        boolQueryBuilder.must(QueryBuilders.boolQuery().should(QueryBuilders.termQuery("companyId", companyId)).should(QueryBuilders.termQuery("sysCourse", "1")));
        return boolQueryBuilder;
    }

    /**
     * 构造高亮器
     *
     * @auther: guangz
     * @date: 2019/8/26 10:42
     */
    private HighlightBuilder createHighlightBuilder(String... fieldNames) {
        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                // .field("productName")
                .preTags("<span style='color:red'>")
                .postTags("</span>");

        // 设置高亮字段
        for (String fieldName : fieldNames) {
            highlightBuilder.field(fieldName);
        }
        return highlightBuilder;
    }
    /*
    QueryBuilders.termQuery("key", obj) 完全匹配
    QueryBuilders.termsQuery("key", obj1, obj2..)   一次匹配多个值
    QueryBuilders.matchQuery("key", Obj) 单个匹配, field不支持通配符, 前缀具高级特性
    QueryBuilders.multiMatchQuery("text", "field1", "field2"..);  匹配多个字段, field有通配符忒行
    QueryBuilders.matchAllQuery();         匹配所有文件
	*/
}
